package com.jinsp.router_compiler;

import com.google.auto.service.AutoService;
import com.jinsp.router_annotation.IntentParameter;
import com.jinsp.router_compiler.constant.APTConstant;
import com.jinsp.router_compiler.util.APTUtils;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;

/**
 * Created by luoshuai on 12/13/21
 * <p>
 * Intent参数注解处理器
 */
@AutoService(Processor.class)
@SupportedAnnotationTypes({"com.jinsp.router_annotation.IntentParameter"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class IntentParameterAnnotationProcessor extends AbstractProcessor {

    private Elements elements;
    private Types types;
    private Messager messager;
    private Filer filer;

    private final Map<TypeElement, List<Element>> intentParameterMap = new HashMap<>();
    private TypeMirror activityTypeMirror;
    private TypeMirror serviceTypeMirror;
    private TypeMirror parcelableTypeMirror;
    private TypeMirror serializableTypeMirror;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elements = processingEnvironment.getElementUtils();
        types = processingEnvironment.getTypeUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        messager.printMessage(Diagnostic.Kind.NOTE, "开始执行IntentParameter注解处理器");
        if (set.isEmpty()) {
            return false;
        }
        //获取支持的Activity or Service的类型，用于校验是否作用在Activity or Service的子类内
        TypeElement activityTypeElement = elements.getTypeElement(APTConstant.ACTIVITY_CLASS_NAME);
        activityTypeMirror = activityTypeElement.asType();
        TypeElement serviceTypeElement = elements.getTypeElement(APTConstant.SERVICE_CLASS_NAME);
        serviceTypeMirror = serviceTypeElement.asType();
        //获取所有IntentParameter注解
        Set<? extends Element> elementsAnnotatedWithIntentParameter = roundEnvironment
                .getElementsAnnotatedWith(IntentParameter.class);
        if (elementsAnnotatedWithIntentParameter != null) {
            for (Element element : elementsAnnotatedWithIntentParameter) {
                //获取该注解的类
                TypeElement classTypeElement = (TypeElement) element.getEnclosingElement();
                String packageName = classTypeElement.getQualifiedName().toString();
                messager.printMessage(Diagnostic.Kind.NOTE,
                        "获取到添加注解IntentParameter的所属类：" + packageName);
                TypeMirror typeMirror = classTypeElement.asType();
                //获取当前IntentParameter注解所属的类是否为Activity or Service
                boolean isActivityType = types.isSubtype(typeMirror, activityTypeMirror);
                boolean isServiceType = types.isSubtype(typeMirror, serviceTypeMirror);
                if (!isActivityType && !isServiceType) {
                    throw new RuntimeException("IntentParameter注解只能作用在Activity或Service内");
                }
                if (intentParameterMap.containsKey(classTypeElement)) {
                    intentParameterMap.get(classTypeElement).add(element);
                } else {
                    List<Element> elements = new ArrayList<>();
                    elements.add(element);
                    intentParameterMap.put(classTypeElement, elements);
                }
            }
            if (!APTUtils.isEmpty(intentParameterMap)) {
                try {
                    createParameterFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    /**
     * 创建参数文件
     *
     * @throws IOException 文件写入异常
     */
    private void createParameterFile() throws IOException {
        //序列化对象描述
        parcelableTypeMirror = elements.getTypeElement(APTConstant.PARCELABLE_CLASS_NAME).asType();
        serializableTypeMirror = elements.getTypeElement(APTConstant.SERIALIZABLE_CLASS_NAME).asType();
        for (Map.Entry<TypeElement, List<Element>> entry : intentParameterMap.entrySet()) {
            TypeElement classTypeElement = entry.getKey();
            TypeMirror classTypeMirror = classTypeElement.asType();
            //判断是Activity or Service
            if (types.isSubtype(classTypeMirror, activityTypeMirror)) {
                //创建Activity对应文件
                createActivityParameterFile(entry.getValue(), classTypeElement);
            } else if (types.isSubtype(classTypeMirror, serviceTypeMirror)) {
                //创建Service对应文件
                createServiceParameterFile(entry.getValue(), classTypeElement);
            }
        }
    }

    /**
     * 创建Service参数传递文件
     *
     * @param intentParameterElements Service内使用的参数注解元素
     * @param classTypeElement        对应的Service类信息
     * @throws IOException 文件写入异常
     */
    private void createServiceParameterFile(List<Element> intentParameterElements,
                                            TypeElement classTypeElement) throws IOException {
        //获取类包名
        String packageName = elements.getPackageOf(classTypeElement).getQualifiedName().toString();
        //构建方法public void getParameter(Object target, Object intent);
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(APTConstant.ROUTER_API_INTENT_PARAMETER_METHOD_NAME)
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(TypeName.OBJECT, APTConstant.ROUTER_API_INTENT_PARAMETER_NAME)
                .addParameter(TypeName.OBJECT, APTConstant.ROUTER_API_INTENT_PARAMETER_INTENT_NAME);
        methodBuilder
                // xxService targetClass = (xxService)target;
                .addStatement("$T $N = ($T)$N", ClassName.get(classTypeElement),
                        APTConstant.ROUTER_API_INTENT_PARAMETER_VARIABLE_NAME,
                        ClassName.get(classTypeElement), APTConstant.ROUTER_API_INTENT_PARAMETER_NAME);
        methodBuilder
                // Intent serviceIntent = (Intent)intent;
                .addStatement("$T $N = ($T)$N", ClassName.get(elements.getTypeElement(APTConstant.INTENT_CLASS_NAME)),
                        APTConstant.ROUTER_API_INTENT_PARAMETER_INTENT_VARIABLE_NAME,
                        ClassName.get(elements.getTypeElement(APTConstant.INTENT_CLASS_NAME)),
                        APTConstant.ROUTER_API_INTENT_PARAMETER_INTENT_NAME);
        for (Element element : intentParameterElements) {
            //获取注解字段名
            String filedName = element.getSimpleName().toString();
            //获取注解信息
            IntentParameter intentParameter = element.getAnnotation(IntentParameter.class);
            String name = intentParameter.name();
            if (APTUtils.isEmpty(name)) {
                name = filedName;
            }
            //获取注解字段信息
            TypeMirror typeMirror = element.asType();
            //类型名，例如java.lang.String
            String typeName = typeMirror.toString();
            //检查变量类型
            TypeKind kind = typeMirror.getKind();
            //构建模板例如：targetClass.xxx = serviceIntent.getIntExtra(name, targetClass.xxx);
            buildMethodContent(methodBuilder, kind, typeMirror, typeName, filedName, name, false);
        }
        String parameterClassName = APTConstant.INTENT_PARAMETER_CLASS_NAME_PREFIX
                + classTypeElement.getSimpleName();
        messager.printMessage(Diagnostic.Kind.NOTE, "开始创建IntentParameter类："
                + packageName + "." + parameterClassName);
        //添加空实现方法
        MethodSpec emptyMethodSpec = MethodSpec.methodBuilder(APTConstant.ROUTER_API_INTENT_PARAMETER_METHOD_NAME)
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(TypeName.OBJECT, APTConstant.ROUTER_API_INTENT_PARAMETER_NAME)
                .build();
        //写入参数文件
        JavaFile.builder(packageName,
                TypeSpec.classBuilder(parameterClassName)
                        .addSuperinterface(ClassName.get(this.elements.getTypeElement(APTConstant.ROUTER_API_INTENT_PARAMETER)))
                        .addModifiers(Modifier.PUBLIC)
                        .addMethod(emptyMethodSpec)
                        .addMethod(methodBuilder.build())
                        .build())
                .build()
                .writeTo(filer);
    }

    /**
     * 创建Activity参数传递文件
     *
     * @param intentParameterElements Activity内使用的参数注解元素
     * @param classTypeElement        对应的Activity类信息
     * @throws IOException 文件写入异常
     */
    private void createActivityParameterFile(List<Element> intentParameterElements,
                                             TypeElement classTypeElement) throws IOException {
        //获取类包名
        String packageName = elements.getPackageOf(classTypeElement).getQualifiedName().toString();
        //构建参数Object target
        ParameterSpec parameterSpec = ParameterSpec.builder(TypeName.OBJECT,
                APTConstant.ROUTER_API_INTENT_PARAMETER_NAME).build();
        //构建方法 public void getParameter(Object target)
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(APTConstant.ROUTER_API_INTENT_PARAMETER_METHOD_NAME)
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(parameterSpec);
        methodBuilder
                // xxActivity targetClass = (xxActivity)target;
                .addStatement("$T $N = ($T)$N", ClassName.get(classTypeElement),
                        APTConstant.ROUTER_API_INTENT_PARAMETER_VARIABLE_NAME,
                        ClassName.get(classTypeElement), APTConstant.ROUTER_API_INTENT_PARAMETER_NAME);
        for (Element element : intentParameterElements) {
            //获取注解字段名
            String filedName = element.getSimpleName().toString();
            //获取注解信息
            IntentParameter intentParameter = element.getAnnotation(IntentParameter.class);
            String name = intentParameter.name();
            if (APTUtils.isEmpty(name)) {
                name = filedName;
            }
            //获取注解字段信息
            TypeMirror typeMirror = element.asType();
            //类型名，例如java.lang.String
            String typeName = typeMirror.toString();
            //检查变量类型
            TypeKind kind = typeMirror.getKind();
            //构建模板例如：targetClass.xxx = targetClass.getIntent().getIntExtra(name, targetClass.xxx);
            buildMethodContent(methodBuilder, kind, typeMirror, typeName, filedName, name, true);
        }
        String parameterClassName = APTConstant.INTENT_PARAMETER_CLASS_NAME_PREFIX
                + classTypeElement.getSimpleName();
        messager.printMessage(Diagnostic.Kind.NOTE, "开始创建IntentParameter类："
                + packageName + "." + parameterClassName);
        //添加空实现方法
        MethodSpec emptyMethodSpec = MethodSpec.methodBuilder(APTConstant.ROUTER_API_INTENT_PARAMETER_METHOD_NAME)
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(TypeName.OBJECT, APTConstant.ROUTER_API_INTENT_PARAMETER_NAME)
                .addParameter(TypeName.OBJECT, APTConstant.ROUTER_API_INTENT_PARAMETER_INTENT_NAME)
                .build();
        //写入参数文件
        JavaFile.builder(packageName,
                TypeSpec.classBuilder(parameterClassName)
                        .addSuperinterface(ClassName.get(this.elements.getTypeElement(APTConstant.ROUTER_API_INTENT_PARAMETER)))
                        .addModifiers(Modifier.PUBLIC)
                        .addMethod(methodBuilder.build())
                        .addMethod(emptyMethodSpec)
                        .build())
                .build()
                .writeTo(filer);
    }

    /**
     * 构建方法内容
     *
     * @param methodBuilder 方法构建者
     * @param kind          参数类型
     * @param typeMirror    参数类型描述
     * @param typeName      参数类型名称
     * @param filedName     成员变量名
     * @param name          参数接收名
     * @param isActivity    是否为Activity使用
     */
    private void buildMethodContent(MethodSpec.Builder methodBuilder,
                                    TypeKind kind, TypeMirror typeMirror,
                                    String typeName, String filedName, String name,
                                    boolean isActivity) {
        String format = null;
        boolean isNeedDefaultValue = true;     //是否需要默认值
        switch (kind) {
            case BYTE:
                format = "$N.$N = $N.getIntent().getByteExtra($S, $N.$N)";
                break;
            case SHORT:
                format = "$N.$N = $N.getIntent().getShortExtra($S, $N.$N)";
                break;
            case INT:
                format = "$N.$N = $N.getIntent().getIntExtra($S, $N.$N)";
                break;
            case FLOAT:
                format = "$N.$N = $N.getIntent().getFloatExtra($S, $N.$N)";
                break;
            case LONG:
                format = "$N.$N = $N.getIntent().getLongExtra($S, $N.$N)";
                break;
            case DOUBLE:
                format = "$N.$N = $N.getIntent().getDoubleExtra($S, $N.$N)";
                break;
            case BOOLEAN:
                format = "$N.$N = $N.getIntent().getBooleanExtra($S, $N.$N)";
                break;
            case ARRAY:
                isNeedDefaultValue = false;
                messager.printMessage(Diagnostic.Kind.NOTE, "参数为数组类型：" + typeName);
                if (typeName.equals(int[].class.getCanonicalName())) {
                    format = "$N.$N = $N.getIntent().getIntArrayExtra($S)";
                } else if (typeName.equals(short[].class.getCanonicalName())) {
                    format = "$N.$N = $N.getIntent().getShortArrayExtra($S)";
                } else if (typeName.equals(float[].class.getCanonicalName())) {
                    format = "$N.$N = $N.getIntent().getFloatArrayExtra($S)";
                } else if (typeName.equals(long[].class.getCanonicalName())) {
                    format = "$N.$N = $N.getIntent().getLongArrayExtra($S)";
                } else if (typeName.equals(double[].class.getCanonicalName())) {
                    format = "$N.$N = $N.getIntent().getDoubleArrayExtra($S)";
                } else if (typeName.equals(byte[].class.getCanonicalName())) {
                    format = "$N.$N = $N.getIntent().getByteArrayExtra($S)";
                } else if (typeName.equals(boolean[].class.getCanonicalName())) {
                    format = "$N.$N = $N.getIntent().getBooleanArrayExtra($S)";
                } else if (typeName.equals(String[].class.getCanonicalName())) {
                    format = "$N.$N = $N.getIntent().getStringArrayExtra($S)";
                }
                break;
            default:
                isNeedDefaultValue = false;
                messager.printMessage(Diagnostic.Kind.NOTE, "参数为其他类型：" + typeName);
                //判断是否为字符串类型
                if (typeName.equals(String.class.getCanonicalName())) {
                    format = "$N.$N = $N.getIntent().getStringExtra($S)";
                } else if (typeName.contains(ArrayList.class.getCanonicalName())) {
                    //获取泛型的类型
                    String[] split = typeName.split("<");
                    String genericType = split[1].split(">")[0];
                    if (genericType.equals(String.class.getCanonicalName())) {
                        format = "$N.$N = $N.getIntent().getStringArrayListExtra($S)";
                    } else if (genericType.equals(Integer.class.getCanonicalName())) {
                        format = "$N.$N = $N.getIntent().getIntegerArrayListExtra($S)";
                    }
                } else if (types.isSubtype(typeMirror, parcelableTypeMirror)) {
                    format = "$N.$N = $N.getIntent().getParcelableExtra($S)";
                } else if (types.isSubtype(typeMirror, serializableTypeMirror)) {
                    format = "$N.$N = $N.getIntent().getSerializableExtra($S)";
                }
                break;
        }
        if (!APTUtils.isEmpty(format)) {
            if (!isActivity) {
                format = format.replace(".getIntent()", "");
            }
            if (isNeedDefaultValue) {
                methodBuilder.addStatement(format,
                        APTConstant.ROUTER_API_INTENT_PARAMETER_VARIABLE_NAME, filedName,
                        isActivity ? APTConstant.ROUTER_API_INTENT_PARAMETER_VARIABLE_NAME :
                                APTConstant.ROUTER_API_INTENT_PARAMETER_INTENT_VARIABLE_NAME,
                        name,
                        APTConstant.ROUTER_API_INTENT_PARAMETER_VARIABLE_NAME, filedName);
            } else {
                methodBuilder.addStatement(format,
                        APTConstant.ROUTER_API_INTENT_PARAMETER_VARIABLE_NAME, filedName,
                        isActivity ? APTConstant.ROUTER_API_INTENT_PARAMETER_VARIABLE_NAME :
                                APTConstant.ROUTER_API_INTENT_PARAMETER_INTENT_VARIABLE_NAME,
                        name);
            }
        } else {
            throw new RuntimeException("不支持传参类型" + typeName);
        }
    }
}
